package com.yugao.fintech.framework.assistant.core.jar;

import com.yugao.fintech.framework.assistant.core.RegexUtils;
import com.yugao.fintech.framework.assistant.core.StringUtils;
import com.yugao.fintech.framework.assistant.core.UUIDUtils;
import com.yugao.fintech.framework.assistant.core.file.FileUtils;
import com.yugao.fintech.framework.assistant.core.file.JarUtils;

import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 通过spring boot maven打包插件进行打包
 * 打包之后的jar包内的目录结构
 * - BOOT-INF
 * - classes
 * - lib
 * - package-name-version.jar
 * - META-INF
 * - org
 * <p>
 * 获取某个依赖jar包中文件路径格式如下:
 * jar:file:/D:/project/my/draper-framework/commons-test/target/commons-test-1.0.0.jar!/BOOT-INF/classes!/
 *

 */
public class JarBuildBySpringBootMavenPlugin extends JarBuildWay {
    /**
     * 目标资源是否在依赖的jar中
     */
    private Boolean isInBootLib = Boolean.TRUE;

    public JarBuildBySpringBootMavenPlugin(CopyResourcesInfo copyResourcesInfo) {
        super(copyResourcesInfo);
        Class<? extends ResourcesInfo> resourceInfoClass = copyResourcesInfo.getResourcesInfo().getClass();
        URL resourceUrl = resourceInfoClass.getResource("");
        if (resourceUrl == null) {
            throw new RuntimeException("get resource [" + resourceInfoClass.getName() + "]  fail");
        }
        this.jarName = getJarName(resourceInfoClass);
    }


    @Override
    protected void doCopyResourcesToLocal() {
        String tmpJarPath = JarResourcesCopyConstant.LIB_CACHE_PATH + File.separator + UUIDUtils.fastUUID() + jarName;
        String currentJarPath = JarUtils.getCurrentJarPath();
        try (JarFile jarFile = new JarFile(currentJarPath)) {
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                String entryName = entry.getName();
                // System.out.println("entryName===> " + entryName);
                if (isInBootLib && entryName.contains("BOOT-INF/lib/" + jarName)) {
                    // 将BOOT-INF/lib/jarName jar包拷贝到本地上
                    InputStream inputStream = jarFile.getInputStream(entry);
                    File file = new File(tmpJarPath);
                    writeFile(inputStream, file);
                    // 从jar中提取文件
                    extractResourcesFromJarFile(tmpJarPath);
                    break;
                } else if (!isInBootLib && entryName.contains("BOOT-INF/classes/")) {
                    // 排除所有.class文件
                    // 排除包含 META-INF 的路径
                    if (entryName.endsWith(".class") || entryName.contains("META-INF")
                            || entry.isDirectory()) {
                        continue;
                    }
                    writeFileFromResource(entryName, jarFile, entry);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            FileUtils.deleteFile(tmpJarPath);
        }
    }

    /**
     * 从jar中提取 resources数据
     *
     * @param jarPath jar包文件路径
     */
    private <T> void extractResourcesFromJarFile(String jarPath) throws Exception {
        try (JarFile targetJarFile = new JarFile(jarPath)) {
            Enumeration<JarEntry> targetEntries = targetJarFile.entries();
            while (targetEntries.hasMoreElements()) {
                JarEntry targetEntry = targetEntries.nextElement();
                // 格式 targetEntryName: test\test.txt
                String targetEntryName = targetEntry.getName().replace("/", File.separator);
                // System.out.println("targetEntryName: " + targetEntryName);

                // 排除目录
                if (targetEntry.isDirectory()) {
                    continue;
                }

                // 排除所有.class文件
                // 排除 BOOT-INF/classes
                // 排除路径包含META-INF的文件
                if (targetEntryName.endsWith(".class") ||
                        targetEntryName.contains("BOOT-INF/classes") ||
                        targetEntryName.contains("META-INF")) {
                    continue;
                }

                writeFileFromResource(targetEntryName, targetJarFile, targetEntry);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取资源路径下的classPath
     *
     * @param targetEntryName
     */
    private String getResourceFileClassPath(String targetEntryName) {
        ResourcesInfo resourcesInfo = copyResourcesInfo.getResourcesInfo();
        // 判断当前entry是否为目标的资源文件
        String classPath = "";
        for (String item : resourcesInfo.classPaths()) {
            // 1. 因为 targetEntryName: test\test.txt, 所以要将用户指定的资源路径的 / 变成 按照系统进行分割
            // 2. 如果是空字符串, 且前面已经将一些资源屏蔽了, 所以只能是resources资源
            item = item.replace("/", File.separator);
            if (("".equals(item) || targetEntryName.contains(item))
                    // 检查是否排除用户指定的文件
                    && !RegexUtils.matches(targetEntryName, this.copyExcludeFiles)) {
                // 如果路径是 BOOT-INF/classes/config.yaml, 去掉 BOOT-INF/classes/
                // 只有资源文件在当前运行的jar包内, 而不是依赖的lib中才会出现这种情况
                classPath = targetEntryName.replace("BOOT-INF/classes/", "").replace("//", File.separator);
                break;
            }
        }
        return classPath;
    }

    /**
     * 将资源中的文件写入到本地
     *
     * @throws Exception
     */
    public void writeFileFromResource(String targetEntryName, JarFile targetJarFile, JarEntry targetEntry) throws Exception {
        String resourceFileClassPath = getResourceFileClassPath(targetEntryName);
        if (StringUtils.isEmpty(resourceFileClassPath)) {
            return;
        }
        TargetFile targetFile = new TargetFile(resourceFileClassPath, copyResourcesInfo);
        InputStream srcInputStream = targetJarFile.getInputStream(targetEntry);
        // System.out.println("targetFullPath: " + targetFile.getFullPath());
        writeFile(srcInputStream, new File(targetFile.getFullPath()));
    }

    /**
     * 获取目标类所在jar的名称
     *
     * @return jar包名
     */
    public String getJarName(Class<?> targetClass) {
        URL resourceUrl = targetClass.getResource("");
        if (resourceUrl == null) {
            throw new RuntimeException("get resource [" + targetClass.getName() + "]  fail");
        }
        // eg1: file:/D:/project/my/draper-framework/commons-test/target/commons-test-1.0.0.jar!/BOOT-INF/classes!/com/project/commons/test/
        String jarFilePath = resourceUrl.getPath();
        // 说明资源就存在所运行的当前jar中, 而不是依赖的lib的jar中
        if (jarFilePath.contains("!/BOOT-INF/classes!")) {
            // 将 !/BOOT-INF/classes!/[packagePath]/ 去掉
            String packagePath = packageToPath(targetClass);
            // 变成 file:/D:/project/my/draper-framework/commons-test/target/commons-test-1.0.0.jar
            String temp = jarFilePath.replace("!/BOOT-INF/classes!" + packagePath, "");
            this.isInBootLib = false;
            return temp.substring(temp.lastIndexOf("/") + 1);
        }

        // eg2: file:/D:/project/my/draper-framework/commons-test/target/commons-test-1.0.0.jar!/BOOT-INF/lib/draper-starter-assistant-1.0.0.jar!/com/project/commons/tool/core/jar/test/
        // 说明资源存在依赖的jar包内
        if (jarFilePath.contains("!/BOOT-INF/lib/")) {
            // 将最后的 !/com/project/commons/tool/core/jar/test/ 去掉
            String packagePath = packageToPath(targetClass);
            String temp = jarFilePath.replace("!" + packagePath, "");
            this.isInBootLib = true;
            return temp.substring(temp.lastIndexOf("/") + 1);
        }
        throw new RuntimeException("get jar name fail");
    }

    /**
     * 比如输入 com.yugao.fintech.framework.assistant.core.jar.test
     * 输出 /cn/lingyangwl/framework/tool/core/jar/test/
     *
     * @return 路径
     */
    private static String packageToPath(Class<?> targetClass) {
        String name = targetClass.getName();
        // 去掉类名, 只保留包名
        String packageName = name.substring(0, name.lastIndexOf("."));
        String[] split = packageName.split("\\.");
        StringBuilder stringBuilder = new StringBuilder("/");
        for (String item : split) {
            stringBuilder.append(item).append("/");
        }
        return stringBuilder.toString();
    }
}
